/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights
 * reserved. <a>http://www.cqcis.com</a>
 */
package com.cqcis.uip.base.framework.handlers.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.mina.common.IoSession;

import com.cqcis.uip.base.framework.FrameworkConstants;
import com.cqcis.uip.base.framework.UIPServiceException;
import com.cqcis.uip.base.framework.base.adaptor.AbstractSocketService;
import com.cqcis.uip.base.framework.common.ControlMsg;
import com.cqcis.uip.base.framework.common.HttpParam;
import com.cqcis.uip.base.framework.common.Role;
import com.cqcis.uip.base.framework.common.Task;
import com.cqcis.uip.base.framework.common.TelnetCommand;
import com.cqcis.uip.base.framework.core.RegisteredServiceContext;
import com.cqcis.uip.base.framework.core.Service;
import com.cqcis.uip.base.framework.core.ServiceRole;
import com.cqcis.uip.base.framework.core.TaskResultProcessor;
import com.cqcis.uip.base.framework.handlers.ClientService;
import com.cqcis.uip.base.framework.handlers.TelnetClientService;
import com.cqcis.uip.base.framework.utils.LogServiceThreadPoolExecutor;
import com.cqcis.uip.base.framework.utils.StringUtil;
import com.cqcis.uip.base.framework.utils.TaskUtil;

/**
 *  telnet客户端适配器, 封装登录、执行真正的业务操作以及退出登录操作
 * 
 * @author huangmy
 * @date   2008-8-10
 */
public class DefaultTelnetClientAdaptor implements ClientService, Service, TaskResultProcessor {
	private static Log log = LogFactory.getLog(DefaultTelnetClientAdaptor.class);
	private final static String END_LINE = "\r\n"; // 行尾符		
	private final static String RECV_INFO_CACHE_BUFFER_TELNET_CLI = "RECV_INFO_CACHE_BUFFER_TELNET_CLI_" + DefaultTelnetClientAdaptor.class.getName();
	private final static String LOGIN_TIMEOUT_CHECK = "LOGIN_TIMEOUT_CHECK_" + DefaultTelnetClientAdaptor.class.getName();
	private final static String CMD_MSG_TIMEOUT_CHECK = "CMD_MSG_TIMEOUT_CHECK_" + DefaultTelnetClientAdaptor.class.getName();
	private final static String CMD_ACK_SEND = "CMD_ACK_SEND_" + DefaultTelnetClientAdaptor.class.getName();
	private TelnetClientService telnetClientService; // telnet服务操作
	private ClientService socketClientService; // socket client服务操作
	private boolean logincheck; // 是否需要登录校验
	private boolean exitAfterCmdExecuted; // 命令执行完毕后执行退出登录命令
	private String loginPrompt; // 输入用户名提示符
	private String passwordPrompt; // 输入密码提示符
	private String userName; // 用户名
	private String password; // 密码
	private String[] prompts; // 命令提示符		
	private long loginTimeout = 30 * 1000; // 登录超时时间, 只客户端发出登录命令后，等待服务端返回超时时间, 该时间一般要小于socket的timeout时间
	private ThreadPoolExecutor timeoutContrlExecutor; // 登录超时，或者接收数据超时控制
	private RegisteredServiceContext serviceContext;// 服务上下文
	
	public boolean processResult(Task task) {
		if (this.telnetClientService instanceof TaskResultProcessor) {
			return ((TaskResultProcessor)this.telnetClientService).processResult(task);
		}
		return false;
	}
	
	public void start(RegisteredServiceContext serviceContext) throws Exception {
		if (this.telnetClientService instanceof Service) {
			((Service)this.telnetClientService).start(serviceContext);
		}
		this.serviceContext = serviceContext;
		// 执行超时监控的线程数与客户端session数一致
		int coreSize = getCoreSize();
		this.timeoutContrlExecutor = LogServiceThreadPoolExecutor.getLogExecutor(coreSize, coreSize * 2, 60, 
				serviceContext, "Telnet Cmd Msg Monitor");
	}
	
	protected int getCoreSize() {
		AbstractSocketService rs = (AbstractSocketService)serviceContext.getRegisteredService();
		if (rs == null) {
			return 2;
		}
		return rs.getClientNumberMax();
	}
	
	public void stop(RegisteredServiceContext serviceContext) throws Exception {
		if (this.timeoutContrlExecutor != null) {
			this.timeoutContrlExecutor.shutdown();
		}
		if (this.telnetClientService instanceof Service) {			
			((Service)this.telnetClientService).stop(serviceContext);
		}
	}
	
	/**
	 * 无效的命令
	 * 
	 * @return
	 */
	private ControlMsg createInvalidCMDControlMsg() {
		ControlMsg cm = new ControlMsg();
		cm.setAttribute(FrameworkConstants.TELNETFIRSTSEND_03, Boolean.TRUE);
		cm.setControlMsg(false);
		return cm;
	}
	
	/**
	 * 正常的数据发送
	 * 
	 * @param task
	 * @return
	 */
	private Object normalTaskMsgSend(final Task task) throws Exception {
		String s = null; // 发送信息
		int state = getLoggingStateFrom(task);		
		if (state == TelnetControlMsg.LOGGING 
				|| state == TelnetControlMsg.LOGOUTING
				|| state == TelnetControlMsg.NOTLOG 
				|| state == TelnetControlMsg.LOGOUT) {				
			// 正在执行操作，等待
			task.setAgain(true, this.loginTimeout, true, false);
			final IoSession session = task.getSession();
			
			Object firstSend03 = session.getAttribute(FrameworkConstants.TELNETFIRSTSEND_03);
			if (firstSend03 == null) {				
				final CountDownLatch loginContrl = new CountDownLatch(1);
				session.setAttribute(LOGIN_TIMEOUT_CHECK, loginContrl);
				this.timeoutContrlExecutor.execute(new Runnable() {// 登录超时监控
					public void run() {
						try {							
							loginContrl.await(loginTimeout, TimeUnit.MILLISECONDS);								
							if (!TaskUtil.isTaskStateInValid(task) 
									&& session.getAttribute(LOGIN_TIMEOUT_CHECK) != null) {								
								serviceContext.catchException(task, new RuntimeException("登录超时,请检查网络配置或者登录提示符配置"));
							}
						} catch (InterruptedException e) {							
						}
					}
				});
				session.setAttribute(FrameworkConstants.TELNETFIRSTSEND_03, new Object());     
				
				return createInvalidCMDControlMsg();// 对于异步接收模式，必须要触发一次虚拟的写操作
			}
			
			return null;			
		} else if (state == TelnetControlMsg.LOGIN_USERNAME_READY) { // 已经准备好登录
			// 未登录或者已经退出登录, 先执行登录			
			task.setAgain(true, this.loginTimeout, true, false);
			registerLoggingStateTo(task, TelnetControlMsg.LOGGING);
			
			if (log.isDebugEnabled()) {
			    log.debug("发送登录用户名");
			}
			
			return toTextline(this.userName);
		} else if (state == TelnetControlMsg.LOGIN_PASSWORD_READY) { // 已经准备好发送密码
			task.setAgain(true, this.loginTimeout, true, false);
			registerLoggingStateTo(task, TelnetControlMsg.LOGGING_OVER);
			
			if (log.isDebugEnabled()) {
				log.debug("发送登录密码");
			}
			
			return toTextline(this.password);
		} else if (state == TelnetControlMsg.LOGIN) {// 已经登录, 直接发送数据
			if (log.isDebugEnabled()) {
			    log.debug("登录状态，等待发送命令");
			}
			s = this.telnetClientService.doTextlineSend(task);
		} else if (state == TelnetControlMsg.LOGOUTING_EXECUTE) {
			return toTextline("exit");
		} else {
			throw new UIPServiceException("状态异常: 无效的链接状态, state=" + state);
		}
		
		if (s != null) {			
			final CountDownLatch contrl = new CountDownLatch(1);
			task.setAttribute(CMD_MSG_TIMEOUT_CHECK, contrl);
			this.timeoutContrlExecutor.execute(new Runnable() {// 命令处理超时监控
				public void run() {
					try {						
						contrl.await(loginTimeout, TimeUnit.MILLISECONDS);						
						if (!TaskUtil.isTaskStateInValid(task) 
								&& task.getAttribute(CMD_MSG_TIMEOUT_CHECK) != null) {							
							serviceContext.catchException(task, new RuntimeException("接收数据超时,请检查命令提示符配置"));
						}						
					} catch (InterruptedException e) {						
					}
				}
			});
		}
				 		
		return toTextline(s);
	}
	
	@SuppressWarnings("unchecked")
	private Map<String, TelnetCommand> createOrGetCmdRepo(Task task) {
		IoSession session = TaskUtil.getCurrentSession(task);
		synchronized (session) {			
			Map<String, TelnetCommand> sendCmdRepo = (Map<String, TelnetCommand>)session.getAttribute("CMD_REPO_TELNET");
			if (sendCmdRepo == null) {
				sendCmdRepo = new HashMap<String, TelnetCommand>();// 发送的命令
				session.setAttribute("CMD_REPO_TELNET", sendCmdRepo);
			}
			
			return sendCmdRepo;
		}		
	}
	
	@SuppressWarnings("unchecked")
	public Object doMessageSend(final Task task) throws Exception {
		if (this.socketClientService != null) {
			Object rtnObj = this.socketClientService.doMessageSend(task);
			if (rtnObj != null) {
				return rtnObj;
			}
		}
		// 正常数据		
		Object sendCmd = normalTaskMsgSend(task);		
		if (sendCmd != null && (sendCmd instanceof ControlMsg)) {
			ControlMsg ctrlMsg = (ControlMsg)sendCmd;
			Object telnetCmd = ctrlMsg.getAttachement();
			if (telnetCmd != null && (telnetCmd instanceof TelnetCommand)) {
				TelnetCommand d = (TelnetCommand)telnetCmd;				
				createOrGetCmdRepo(task).put(String.valueOf(d.getParam()), d);
				return ctrlMsg;
			}
		}
			
		return sendCmd;
	}
	
	/**
	 * 发送字符串
	 * 
	 * @param s
	 * @return
	 */
	private String toTextline(String s) {
		if (s != null && !s.endsWith(END_LINE)) {
			s = s + END_LINE;
		}
		
		return s;
	}
	
	/**
	 * 登录成功
	 * 
	 * @param task
	 */
	private void loginCountDown(Task task) {
		CountDownLatch loginContrl = (CountDownLatch)TaskUtil.getCurrentSession(task).removeAttribute(LOGIN_TIMEOUT_CHECK);
		if (loginContrl != null) {			
		    loginContrl.countDown();
		}
	}
	
	/**
	 * 登录成功
	 * 
	 * @param task
	 */
	private void messageReceivedCountDown(Task task) {
		CountDownLatch contrl = (CountDownLatch)task.removeAttribute(CMD_MSG_TIMEOUT_CHECK);
		if (contrl != null) {
		    contrl.countDown();		
		}
	}
	
	/**
	 * 正常的接收到的文本字符串
	 * 
	 * @param task 
	 * @param info
	 */
	private void normalTaskReceivedMsg(Task task, String info, Object clusterObj) throws Exception {		
		int state = getLoggingStateFrom(task);		
		if (state == TelnetControlMsg.NOTLOG) {// 状态为未登录，但接收到数据，此时将数据视为提示登录信息,发送登录用户名
			donotExecuteTaskDefault(task);
			if (!this.logincheck) { // 不需要登录校验				
				if (!isLoginSuccess(info)) {// 登录未成功					
					return;
				}
								
				registerLoggingStateTo(task, TelnetControlMsg.LOGIN);				
				loginCountDown(task); // 登录成功					
				TaskUtil.notifyTaskWaiter(task);				
			} else if (info.indexOf(this.loginPrompt) >= 0) {// 可以输入用户名
			    registerLoggingStateTo(task, TelnetControlMsg.LOGIN_USERNAME_READY);			    
			    TaskUtil.notifyTaskWaiter(task);
			}			
			return;
		} else if (state == TelnetControlMsg.LOGGING) {// 正在登录, 发送密码
			donotExecuteTaskDefault(task);
			if (info.indexOf(this.passwordPrompt) >= 0) {// 可以输入密码
			    registerLoggingStateTo(task, TelnetControlMsg.LOGIN_PASSWORD_READY);			    
			    TaskUtil.notifyTaskWaiter(task);
			}			
			return;
		} else if (state == TelnetControlMsg.LOGGING_OVER) { // 登录完成，结果校验	
			donotExecuteTaskDefault(task);
			if (isLoginSuccess(info)) {// 登录成功, 进行正常的业务层命令发送
				loginCountDown(task); // 登录成功
				if (log.isDebugEnabled()) {
				    log.debug("登录成功");
				}
				registerLoggingStateTo(task, TelnetControlMsg.LOGIN);				
				TaskUtil.notifyTaskWaiter(task);				
			}
			return;
		}
		
		// 因为telnet读取是基于行读取的，有一个标志prompt标机是否完成，而提供给业务层使用的数据必须完整的应答数据，因而，
		// 必须对字符串进行缓存
		if (info != null && info.length() > 0) {
			info = cacheOrGetInfo(info, task);
		    if (info == null) {
		    	donotExecuteTaskDefault(task);
				return;
		    }
		    
		    messageReceivedCountDown(task); // 接收到数据
		    
		    if (log.isDebugEnabled()) {
				log.debug("接收到命令处理结果数据");
			}
		    
			this.telnetClientService.doTextlineReceived(info, task);				
					
			// 判定是否发送退出登录命令
			if (this.exitAfterCmdExecuted) {
				task.setAgain(true, 0, true, true);
				registerLoggingStateTo(task, TelnetControlMsg.LOGOUTING_EXECUTE);			
			}
		} else if (this.socketClientService != null && clusterObj != null) {
			this.socketClientService.doMessageReceived(clusterObj, task);
		}
	}
	
	/**
	 * 获取或者缓存数据
	 * 
	 * @param info
	 * @return
	 */
	private String cacheOrGetInfo(String info, Task task) {		
		StringBuffer b = (StringBuffer)task.getAttribute(RECV_INFO_CACHE_BUFFER_TELNET_CLI);
		if (b == null) {
			// 新查找info中是否有提示符
			if (hasPrompt(info)) {
				return info;
			}
			// 否则, 新建缓存，并缓存数据
			b = new StringBuffer();
			task.setAttribute(RECV_INFO_CACHE_BUFFER_TELNET_CLI, b);
			b.append(info);
		} else {
			// 有buffer, 先缓存，再判断是否提示符，有，则返回
			b.append(info);
			if (hasPrompt(info)) {				
				String cacheInfo = b.toString();
				task.removeAttribute(RECV_INFO_CACHE_BUFFER_TELNET_CLI);
				return cacheInfo;
			}
		}
		
		return null;
	}

	/**
	 * 不执行默认方法
	 * 
	 * @param task
	 */
	private void donotExecuteTaskDefault(Task task) {
		task.setExecuteDefault(false); // 不执行默认的系统行为
		task.setGenerateResult(false); // 不生成结果
	}
	
	/**
	 * 接收到的telnet命令处理
	 * 
	 * @param task
	 * @param cmd
	 */
	@SuppressWarnings("unchecked")
	private void telnetCommandTaskReceivedMsg(Task task, TelnetCommand cmd) {
		TelnetCommand rspCmd = null; // 返回命令
		byte c = cmd.getCmd(); // 命令
		byte p = cmd.getParam(); // 选项
		byte[] sub = cmd.getSubCmdContext();
				
		String paramKey = String.valueOf(p);
		
		TelnetCommand sentCmd = createOrGetCmdRepo(task).remove(paramKey);
		if (sentCmd != null) {
			if (log.isDebugEnabled()) {
			    log.debug("命令: " + cmd + " 为选项协商命令: " + sentCmd + " 的响应, 忽略响应");
			}
					
			return; // 应答, 忽略
		}
						
		switch (c) {
		case TelnetCommand.SB :// 子命令, 处理方式与 DO 一致
		case TelnetCommand.DO :			
			if (p == TelnetCommand.TERMIAL_TYPE && sub != null 
					&& sub.length >=2 && sub[0] == TelnetCommand.TERMIAL_TYPE
					&& sub[1] == TelnetCommand.ASK) { // 询问终端类型
				byte[] ttyRsp = new byte[]{(byte)0x18,(byte)0x00,
						(byte)0x76,(byte)0x74,(byte)0x31,(byte)0x30,(byte)0x30};// 回答为vt100			
				rspCmd = new TelnetCommand(TelnetCommand.SB, (byte)0, ttyRsp);
			} else if (p == TelnetCommand.TERMIAL_TYPE && sub == null) {
				rspCmd = new TelnetCommand(TelnetCommand.WILL, p, null);
			} else if (p == TelnetCommand.PARAM_WINDOW && sub == null) { // 窗口大小
				byte[] winRsp = new byte[] {(byte)0x1F, (byte)0x00, (byte)0x84, (byte)0x00, (byte)0x2E};
				rspCmd = new TelnetCommand(TelnetCommand.WILL, p, winRsp);
			} else {
				rspCmd = new TelnetCommand(TelnetCommand.WONT, p, null);
			}
						
			break;
		case TelnetCommand.DONT :
			rspCmd = new TelnetCommand(TelnetCommand.WONT, p, null);
			break;
		case TelnetCommand.WILL :
			rspCmd = new TelnetCommand(TelnetCommand.DONT, p, null);
			break;
		case TelnetCommand.WONT :
			rspCmd = new TelnetCommand(TelnetCommand.DONT, p, null);
			break;	
		default:
			throw new UIPServiceException("无效的telnet协商命令: " + StringUtil.toHexString(c, true));
		}
		
		if (log.isDebugEnabled()) {
		    log.debug("生成选项协商应答命令: " + rspCmd);
		}
				
		TaskUtil.write(task, createCMDControlMsg(rspCmd));		
		
		IoSession sesion = task.getSession();
		if (sesion.getAttribute(CMD_ACK_SEND) == null) {
			sesion.setAttribute(CMD_ACK_SEND, new Object());
			rspCmd = new TelnetCommand(TelnetCommand.DO, (byte)0x03, null);	
			createOrGetCmdRepo(task).put(String.valueOf(rspCmd.getParam()), rspCmd);
			TaskUtil.write(task, createCMDControlMsg(rspCmd));
		}
	}
	
	/**
	 * 创建命令
	 * 
	 * @param rspCmd
	 * @return
	 */
	private ControlMsg createCMDControlMsg(TelnetCommand rspCmd) {
		ControlMsg cm = new ControlMsg();
		cm.setAttachement(rspCmd);
		cm.setControlMsg(false);
		return cm;
	}
	
	/**
	 * telnet 操作，当链接建立时，服务端会首先发送提示登录信息到客户端，（用户名输入提示符，此时，客户端应该发送用户名，
	 * 当提示输入密码时候，继续发送密码，最后判别是否登录成功）
	 */
	public void doMessageReceived(Object message, Task task) throws Exception {		
		ControlMsg cmsg = (ControlMsg)message;		
		TelnetCommand cmd = (TelnetCommand)cmsg.getAttachement();
		if (cmd != null) { // 命令			
			donotExecuteTaskDefault(task);
			telnetCommandTaskReceivedMsg(task, cmd);
			return;
		}
		
		Object switchMsg = cmsg.getAttribute(FrameworkConstants.CLUSTER_SWITCH);
		String info = cmsg.getMessage();	
		if (log.isDebugEnabled()) {
			log.debug("RECV INFO: \n" + info + (switchMsg == null ? "" : 
				(switchMsg instanceof HttpParam ? ((HttpParam)switchMsg).getHeadersString() 
						: switchMsg.toString())));
		}
				
		normalTaskReceivedMsg(task, info, switchMsg);
	}
	
	/**
	 * 是否登录成功
	 * 
	 * @param loginText
	 * @return
	 */
	private boolean isLoginSuccess(String loginText) {
		return hasPrompt(loginText);
	}
	
	/**
	 * 提示符
	 * 
	 * @param text
	 * @return
	 */
	private boolean hasPrompt(String text) {
		text = StringUtil.trimWhiteToNull(text);
		if (text == null) {
			return false;
		}
		for (String s : this.prompts) {
			if (text.endsWith(s)) { // 成功
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * 获取登录状态
	 * 
	 * @param task
	 * @return
	 */
	protected int getLoggingStateFrom(Task task) {		
		synchronized (task) {
			String key = TelnetControlMsg.class.getName();			
			IoSession session = task.getSession();			
			TelnetControlMsg tcm = (TelnetControlMsg)session.getAttribute(key);
			if (tcm == null) {
				tcm = new TelnetControlMsg();
				session.setAttribute(key, tcm);
			}
			
			return tcm.getState();
		}		
	}
	
	/**
	 * 设置状态
	 * 
	 * @param task
	 * @param state
	 */
	private void registerLoggingStateTo(Task task, int state) {
		IoSession session = TaskUtil.getCurrentSession(task);
		TelnetControlMsg tcm = (TelnetControlMsg)session.getAttribute(TelnetControlMsg.class.getName());
		if (tcm == null) {
			tcm = new TelnetControlMsg();
			session.setAttribute(TelnetControlMsg.class.getName(), tcm);
		}
		
		tcm.setState(state);
	}
	
	public static class TelnetControlMsg {
		public final static int NOTLOG = 0; // 未登录
		public final static int LOGGING = 1; // 正在登录
		public final static int LOGIN = 2; // 已经登录
		public final static int LOGOUTING = 3; //  正在退出登录
		public final static int LOGOUT = 4; // 已经退出登录
		
		public final static int LOGIN_USERNAME_READY = 5; // 提示输入用户名
		public final static int LOGIN_PASSWORD_READY = 6; // 提示输入密码
		
		public final static int LOGGING_OVER = 7; // 登录结束
		public final static int LOGOUTING_EXECUTE = 8; // 执行退出登录
		
		private int state = NOTLOG;
		
		public int getState() {
			return state;
		}
		public void setState(int state) {
			this.state = state;
		}
	}

	@ServiceRole(Role.TELNET_CLIENT)
	public void setTelnetClientService(TelnetClientService telnetClientService) {
		this.telnetClientService = telnetClientService;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setLogincheck(boolean logincheck) {
		this.logincheck = logincheck;
	}

	public void setExitAfterCmdExecuted(boolean exitAfterCmdExecuted) {
		this.exitAfterCmdExecuted = exitAfterCmdExecuted;
	}

	/**
	 * 登录成功标志
	 * 
	 * @param loginSucMarks
	 */
	public void setPrompt(String prompts) {		
		this.prompts = prompts.split(",");
		if (this.prompts == null || this.prompts.length <= 0) {
			throw new RuntimeException("登录成功校验预置条件错误, prompt: " + prompts);
		}
	}

	public void setLoginPrompt(String loginPrompt) {
		this.loginPrompt = loginPrompt;
	}

	public void setPasswordPrompt(String passwordPrompt) {
		this.passwordPrompt = passwordPrompt;
	}

	public void setLoginTimeout(long loginTimeout) {
		this.loginTimeout = loginTimeout;
	}

	public void setSocketClientService(ClientService socketClientService) {
		this.socketClientService = socketClientService;
	}
}
